package org.september.pisces.operation.service;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ZipUtil;
import lombok.extern.slf4j.Slf4j;
import org.september.pisces.operation.enums.OperationSettingEnum;
import org.september.pisces.operation.util.DatasourceConfigUtil;
import org.september.pisces.settings.entity.SystemSettings;
import org.september.pisces.settings.service.PiscesSettingService;
import org.september.smartdao.CommonDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Service
@Slf4j
public class DbBackupService {

    @Autowired
    PiscesSettingService settingService;

    @Autowired
    CommonDao commonDao;

    @Value("${spring.alliance.dao.dialect:mysql}")
    private String dialect;

    private static final long maxSize = 1000L * 1024 * 1024; //1G

    private static final Pattern mysql_url_pattern =
            Pattern.compile("jdbc:mysql://([^:/]+)(?::(\\d+))?/([^?]+).*");

    public void dbBackup() {
        String backupDate = settingService.getValueOrDefault(OperationSettingEnum.数据库备份周期);
        if (backupDate.contains("|")) {
            String[] parts = backupDate.split("\\|");
            int intervalDays = Integer.parseInt(parts[0]);
            String lastBackupDate = parts[1];

            Calendar calendar = Calendar.getInstance();
            calendar.setTime(DateUtil.parse(lastBackupDate, "yyyy-MM-dd"));
            calendar.add(Calendar.DAY_OF_MONTH, intervalDays);

            Calendar today = Calendar.getInstance();
            today.set(Calendar.HOUR_OF_DAY, 0);
            today.set(Calendar.MINUTE, 0);
            today.set(Calendar.SECOND, 0);
            today.set(Calendar.MILLISECOND, 0);
            if (!calendar.before(today)) {
                return;
            }

        }
        boolean result = true;
        String saveFilePath = settingService.getValueOrDefault(OperationSettingEnum.数据库备份路径);
        saveFilePath += "//" + DateUtil.format(new Date(), "yyyy-MM-dd");
        List<DatasourceConfigUtil.DataSourceConfig> list = DatasourceConfigUtil.getDatasource();
        for (DatasourceConfigUtil.DataSourceConfig config : list) {
            if (!"mysql".equalsIgnoreCase(dialect)) {
                log.info("不支持的数据库备份类型：" + dialect);
                break;
            }
            Matcher matcher = mysql_url_pattern.matcher(config.getJdbcUrl());
            if (!matcher.matches()) {
                log.info("数据库jdbcUrl配置错误：" + config.getJdbcUrl());
                continue;
            }
            String host = matcher.group(1);
            String port = matcher.group(2);
            String database = matcher.group(3);

            FileUtil.mkdir(saveFilePath);
            try {
                ProcessBuilder pb = new ProcessBuilder(
                        "mysqldump",
                        "-u" + config.getUsername(),
                        "-p" + config.getPassword(),
                        "-h" + host,
                        "-P" + port,
                        "--single-transaction",
                        "--master-data=2",
                        "--databases", database
                );

                pb.redirectErrorStream(true);

                Process process = pb.start();

                int fileIndex = 1;
                long currentSize = 0;
                File outFile = new File(saveFilePath, String.format("db_%s_%d.sql", database, fileIndex));
                BufferedWriter writer = new BufferedWriter(
                        new OutputStreamWriter(new FileOutputStream(outFile), StandardCharsets.UTF_8), 1024 * 1024);

                try (BufferedReader br = new BufferedReader(
                        new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8), 1024 * 1024)) {
                    String line;
                    while ((line = br.readLine()) != null) {
                        byte[] data = (line + System.lineSeparator()).getBytes(StandardCharsets.UTF_8);

                        if (currentSize + data.length > maxSize && line.trim().endsWith(";")) {
                            writer.close();
                            log.info("生成数据库备份分片: " + outFile.getAbsolutePath());
                            fileIndex++;
                            outFile = new File(saveFilePath, String.format("db_%s_%d.sql", database, fileIndex));
                            writer = new BufferedWriter(
                                    new OutputStreamWriter(new FileOutputStream(outFile), StandardCharsets.UTF_8), 1024 * 1024);
                            currentSize = 0;
                        }

                        writer.write(line);
                        writer.newLine();
                        currentSize += data.length;
                    }
                }

                writer.close();
                int exitCode = process.waitFor();
                if (exitCode == 0) {
                    log.info(database + " 数据库备份完成，共生成 " + fileIndex + " 个文件。");
                } else {
                    log.error("mysqldump 执行失败，exitCode=" + exitCode);
                    result = false;
                    break;
                }
            } catch (Exception e) {
                log.error("数据库备份异常：", e);
                result = false;
            }
        }
        if (result) {
            ZipUtil.zip(saveFilePath);
            FileUtil.del(saveFilePath);
            String date = DateUtil.format(new Date(), "yyyy-MM-dd");
            backupDate = backupDate.contains("|") ? backupDate.split("\\|")[0] : backupDate;
            backupDate += "|" + date;
            SystemSettings settings = new SystemSettings();
            settings.setCfgKey(OperationSettingEnum.数据库备份周期.getKey());
            settings.setGroupName(OperationSettingEnum.数据库备份周期.getGroup());
            settings = commonDao.getByExample(settings);
            settings.setCfgVal(backupDate);
            commonDao.update(settings);
        }
    }
}
